/*------------------------------------------------------------------------------
 *  SEdge - Schematic Edge class.
 *  
 *  August 2009, Forhad Ahmed
 *------------------------------------------------------------------------------
 */

package schematic;

import geometry.CLine;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.Vector;

import vector.vector;


public class SEdge extends SObject {
    /*
     * Constructor
     */
    public SEdge() {
        super();
        initEdge();
    }
    
    
    /*
     * Explicit-id constructor
     */
    public SEdge(int i) {
        super(i);
        //initEdge();
    }
    
    
    /*
     * Initialize the edge data structures
     */
    public void initEdge() {
        int index;
        
        multipoint = false;
        head = null;
        tail = null;
        body = new vector("edge-body");
        temp = new Point(-1,-1);
        unified_move = false;
        tsegment = new Line2D.Double();
        segments = new CLine[max_segments];
        suggestion = null;
        save_orientation = new Vector();
        temp_container = new Vector();
        
        for (index = 0; index < max_segments; index++) {
            segments[index] = new CLine();
        }
    }
    
    
    /*
     * Return a copy of the object to be used for checkpointing
     */
    public SObject checkpoint(int action) {
        SEdge checkpoint;
        checkpoint = new SEdge(id);
        checkpoint.original = this;
        
        /*
         * Only calculate copies of internal parts if its a mod operation
         */
        if (action == Schematic.CHECKPOINT_MOD) {
            checkpoint.head = head;
            checkpoint.tail = tail;
            checkpoint.body = cloneBody();
            
            temp_head = head;
            temp_tail = tail;
        }
     
        return checkpoint;
    }
    
    
    /*
     * Revert this modified object back to it's checkpointed state
     */
    public void revert(SObject object) {
        SEdge checkpoint = (SEdge) object;
        head = checkpoint.head;
        tail = checkpoint.tail;
        body = checkpoint.body;
    }
    
    
    /*
     * The main changes in an edge object are the end mappings and the body.  We
     * store old/new mappings of the ends in the diff object.  For storing the 
     * body diff, we could just store the old/new body objects and be done but 
     * there are some common cases where we can optimize:
     * 
     * - If the edge has a zero length body, then we don't need to allocate 2 
     * zero length vectors (old/new) and store them in this diff object.  In 
     * this case, old/new can both be null indicating that both the old edge and 
     * the new edge have no body.
     * 
     * - If the edge has a body and the body points are only translated (i.e. 
     * body point count is the same before/after) then we can store a move 
     * vector for the body in the diff object.
     * 
     * - If the edge has a body and the body points were added/removed, that's 
     * the only case where we need to store 2 body vectors for before/after.
     */
    public SObject diff(SObject edge) {
        SEdge original;
        Point cp, op;
        int index;
        
        original = (SEdge) edge;
        
        if (checkpoint == Schematic.CHECKPOINT_MOD) {
            
            head = this.head;
            tail = this.tail;
            new_head = original.head;
            new_tail = original.tail;
            
            if (body.size() == 0 && original.body.size() == 0) {
                body = null;
                new_body = null;
            } else if (body.size() == original.body.size()) {
                /*
                 * Turn the old body into a translation vector
                 */
                for (index = 0; index < body.size(); index++) {
                    cp = (Point) body.get(index);
                    op = (Point) original.body.get(index);
                    cp.x = op.x - cp.x;
                    cp.y = op.y - cp.y;
                }
                
                /*
                 * new_body pointer still stays null
                 */
                new_body = null;
            } else {
                /*
                 * Store both bodies
                 */
                body = this.body;
                new_body = original.cloneBody();
            }
        }
        
        return this;
    }
    
    
    /*
     * Reverse patch this edge, given a diff edge
     */
    public void rpatch(SObject object, Schematic schematic) {
        SEdge diff;
        int index;
        Point dp, p;
        
        diff = (SEdge) object;
        
        if (diff.checkpoint == Schematic.CHECKPOINT_MOD) {
            head = diff.head;
            tail = diff.tail;
            
            if (diff.body == null && diff.new_body == null) {
                return;
            } else if (diff.body != null && diff.new_body == null) {
                /*
                 * Since diff.body is a translation vector, we just need to 
                 * offset our own body object by diff.body
                 */
                for (index = 0; index < diff.body.size(); index++) {
                    dp = (Point) diff.body.get(index);
                    p =  (Point) body.get(index);
                    p.x = p.x - dp.x;
                    p.y = p.y - dp.y;
                }
            } else {
                copyBody(diff.body);
            }
        } else if (diff.checkpoint == Schematic.CHECKPOINT_ADD) {
            schematic.remove_internal(diff.original);
        } else if (diff.checkpoint == Schematic.CHECKPOINT_DEL) {
            schematic.add_internal(diff.original);
        }
    }
    
    
    /*
     * Patch this edge, given a diff edge
     */
    public void patch(SObject object, Schematic schematic) {
        SEdge diff;
        int index;
        Point dp, p;
        
        diff = (SEdge) object;
        
        if (diff.checkpoint == Schematic.CHECKPOINT_MOD) {
            head = diff.new_head;
            tail = diff.new_tail;
            
            if (diff.body == null && diff.new_body == null) {
                return;
            } else if (diff.body != null && diff.new_body == null) {
                /*
                 * Since diff.body is a translation vector, we just need to 
                 * offset our own body object by diff.body
                 */
                for (index = 0; index < diff.body.size(); index++) {
                    dp = (Point) diff.body.get(index);
                    p =  (Point) body.get(index);
                    p.x = p.x + dp.x;
                    p.y = p.y + dp.y;
                }
            } else {
                copyBody(diff.new_body);
            }
        } else if (diff.checkpoint == Schematic.CHECKPOINT_ADD) {
            schematic.add_internal(diff.original);
        } else if (diff.checkpoint == Schematic.CHECKPOINT_DEL) {
            schematic.remove_internal(diff.original);
        }
    }
    
    
    /*
     * Make a copy of the body vector for this edge.
     */
    public vector cloneBody() {
        int index;
        Point  p;
        vector newbody = new vector("edge-body-clone" + " " + id);
        
        for (index = 0; index < body.size(); index++) {
            p = (Point) body.get(index);
            newbody.add(new Point(p.x, p.y));
        }
        
        return newbody;
    }
    
    
    /*
     * Make a copy of the new_body vector for this edge.
     * XXX: might be obsolete; nuke
     */
    public vector cloneNewBody() {
        int index;
        Point  p;
        vector newbody = new vector("edge-body-clone" + " " + id);
        
        for (index = 0; index < new_body.size(); index++) {
            p = (Point) new_body.get(index);
            newbody.add(new Point(p.x, p.y));
        }
        
        return newbody;
    }
    
    
    /*
     * Given a vector of body points, (deep) copy those points into the body
     */
    public void copyBody(vector bpoints) {
        int index;
        Point p;
        
        body.clear();
        for (index = 0; index < bpoints.size(); index++) {
            p = (Point) bpoints.get(index);
            body.add(new Point(p.x, p.y));
        }
    }
    
    
    /*
     * Get the *schematic* object for a given end
     */
    public SObject endobject(Object end) {
        SNodeTerminal t;
        SJunction j;
        
        if (end instanceof SNodeTerminal) {
            t = (SNodeTerminal) end;
            return t.parent;
        }
        
        if (end instanceof SJunction) {
            j = (SJunction) end;
            return j;
        }
        
        return null;
    }
    
    
    /*
     * Given an end (head = 0 , tail = 1), link the specified object with that
     * end.  From the edge object's perspective, we just set the head/tail to
     * point to that object, but we also have to link that object (different way
     * for different object types) with this edge.  Return false if the linkage
     * is illegal (head = tail = same terminal/junction, etc)
     * 
     * We need to pass the schematic here because the link operation may change 
     * up objects that need to be checkpointed by the schematic.
     */
    public boolean link(int end, Object object, Schematic schematic) {
        SNode         node;
        SNodeTerminal terminal;
        SJunction     junction;
        SEdge         edge;
        SEdge         newedge;
        Point         point;
        int           bindex = -1;
        boolean       corner = false;

        if (object instanceof SNode || object instanceof SNodeTerminal) {
            /*
             * If we are linking the end with a schematic node, we aren't 
             * *really* linking with the node but instead, one of the node's 
             * terminals. The terminal to link to is the cached 'suggest' 
             * terminal that is stored in the node object.
             */
            if (object instanceof SNode) {
                node = (SNode) object;
                terminal = node.suggest_terminal;
            } else {
                terminal = (SNodeTerminal) object;
            }
            
            if (end == 0) {
                head = terminal;
                terminal.edges.add(this);
                return true;
            }
            
            /*
             * Can't link the edge's opposite ends on same node terminal!
             */
            if (end == 1) {
                if (head != terminal) {
                    tail = terminal;
                    terminal.edges.add(this);
                    return true;
                } else {
                    return false;
                }
            }
            
        } else if (object instanceof SJunction) {
            junction = (SJunction) object;

            if (end == 0) {
                head = object;
                junction.edges.add(this);
                return true;
            }
            
            /*
             * Can't link the edge's opposite ends on same junction!
             */
            if (end == 1) {
                if (head != junction) {
                    tail = junction;
                    junction.edges.add(this);
                    return true;
                } else {
                    return false;
                }
            }
        } else if (object instanceof SEdge) {
            edge = (SEdge) object;
            point = edge.suggestion;
            
            /*
             * If 'this' edge is the same as the one we are linking against or
             * if the suggestion point of the edge we are linking against is 
             * null, then we block this operation.
             * 
             * TODO: also block this operation if it will form a future loop!
             */
            if (edge == this) return false;
            if (point == null) return false;
            
            /*
             * Get the body index (corner or segment) of the edge we are linking
             * against.
             */
            if (edge.suggest_corner  >= 0) {
                bindex = edge.suggest_corner;
                corner = true;
            } else if (edge.suggest_segment >= 0) {
                bindex = edge.suggest_segment;
            }
            
            junction = new SJunction(point.x, point.y);
            link(end, junction, null);
            
            
            if (edge.tail instanceof SNodeTerminal) {
                terminal = (SNodeTerminal) edge.tail;
                schematic.checkpoint(terminal.parent, 0);        
            } else if (edge.tail instanceof SJunction) {
                schematic.checkpoint((SJunction) edge.tail, 0);
            }

            newedge = edge.split(bindex, junction, corner);
        
            if (newedge != null) {
                schematic.checkpoint(newedge, 1);
                schematic.checkpoint(junction, 1);
                schematic.add_internal(newedge);
                schematic.add_internal(junction);
                return true;
            } 
        }
        
        return false;
    }
    
    
    /*
     * Given an end, unlink the specified object with that end.  Unlinking 
     * involves:
     *    
     * 1) Nulling the head/tail pointer of the edge 
     * 2) Removing the edge from the head/tail object
     * 
     * (1) happens if fromedge is true.  (2) happens if toedge is true.  Both 
     * happen if both fromedge/toedge is true
     */
    public void unlink(int end, Object obj, boolean fromedge, boolean toedge) {
        SNodeTerminal terminal;
        SJunction     junction;
        
        if (fromedge) {
            if (end == 0) head = null; else tail = null;
        }
        
        if (toedge) {
            if (obj instanceof SNodeTerminal) {
                terminal = (SNodeTerminal) obj;
                if (terminal.edges.remove(this) == false) {
                    assert(false);
                }
                
            } else if (obj instanceof SJunction) {
                junction = (SJunction) obj;
                if (junction.edges.remove(this) == false) {
                    assert(false);
                }
            }    
        }
    }
    
    
    /*
     * Given a junction and a segment index to split on, split this edge by that
     * junction and return the newly created edge
     */
    public SEdge split(int bindex, SJunction j, boolean corner) {
        int index;
        int count = 0;
        SEdge edge = null;
        Object original_tail = tail;  /* Cache the original tail value */

        /*
         * Remove the body points from 'this' after the split point and keep
         * them in a temp container to be used for adding to the newly
         * created edge.
         */
        temp_container.clear();  
        count = body.size() - bindex;
        while (count-- > 0) {
            temp_container.add(body.remove(bindex));
        }

        /*
         * Unlink the tail pointer from the current tail object and link it
         * to the split junction, and we are done with the split work for 
         * 'this' edge.
         */
        unlink(1, tail, true, true);
        link(1, j, null);

        /*
         * Now we create a new edge to form the second half of the split, 
         * link head with junction, add the removed body points from the
         * first edge to this edge, and then link the tail to the cached
         * tail.
         */
        edge = new SEdge();
        edge.link(0, j, null);

        for (index = corner ? 1 : 0; index < temp_container.size(); index++) {
            edge.body.add(temp_container.get(index));
        }

        edge.link(1, original_tail, null);

        return edge;
    }

    
    /*
     * Merge 2 edges that are improperly joined at a junction. We need the merge 
     * operation for junctions that have exactly 2 edges linked to them; these 
     * not proper formations and only occur as a temporary artifact of some 
     * other action such as delete/move, etc.
     * 
     * Merging of edges usually takes place at the end of an action/transaction 
     * because we want all the moves/deletes to happen first, otherwise the 
     * objects can get into an inconsistent state.
     * 
     * Note that although the 2 given edges share a common junction, their other 
     * ends *must* not be the same, or else merging them will create a loop.  
     * This should not happen!
     */
    public static boolean merge(SEdge edge, 
                                SEdge dedge, 
                                SJunction djunction, 
                                Schematic schematic) {
        SObject  endobject;
        int      other_end;
        int      end;
                
        assert(edge.head  == djunction || edge.tail  == djunction);
        assert(dedge.head == djunction || dedge.tail == djunction);
        
 
        /*
         * First remove the delete-junction.
         */
        schematic.checkpoint(djunction, -1);
        schematic.remove_internal(djunction);
        
        /*
         * Modify the non-delete edge: modify it's body and unlink it from the
         * end that is attached to the delete-junction.
         */
        schematic.checkpoint(edge, 0);
        mergeBody(edge, dedge, djunction);
        if (edge.head == djunction) end = 0; else end = 1;
        if (end == 0) other_end = 1; else other_end = 0;
        edge.unlink(end, djunction, true, false);
          
        /*
         * Unlink the end object who was opposite to the delete-junction-end on
         * the delete-edge.
         */
        if (dedge.head == djunction) end = 1; else end = 0;
        endobject = dedge.endobject(end == 1 ? dedge.tail : dedge.head);
        schematic.checkpoint(endobject, 0);
        dedge.unlink(end, endobject, false, true);
        
        /*
         * Remove the delete-edge.
         */
        schematic.checkpoint(dedge, -1);
        schematic.remove_internal(dedge);
        
        /*
         * 
         */
        edge.link(other_end, endobject, schematic);
        
        return true;
    }
    
    
    /*
     * Given src and dst edges that are connected together at a junction, copy 
     * the body points of the src edge and place them inside the dst edge.  Src
     * edge is always modified.  Dst edge is untouched. Note that we must always
     * add the 'junction' point to the dst edge body. Subsequently after all the 
     * merges have been complete, if this junction point happened to be a
     * coincident point on an end, it will be flattened in the flattening phase.
     * 
     * TODO: compact the cases
     */
    public static void mergeBody(SEdge dst, SEdge src, SJunction j) {
        int index;
        Point p;
        
        /*
         * Simple case of extending the dst body with the src body.
         */
        if (src.tail == j && dst.head == j) {
            for (index = 0; index < src.body.size(); index++) {
                p = (Point) src.body.get(index);
                dst.body.add(new Point(p.x, p.y));
                dst.body.add(new Point(j.p.x, j.p.y));
            }
        } 
        
        /*
         * Take the src body points and add them to the beginning of dst body
         */
        if (src.head == j && dst.head == j) {
            for (index = 0; index < src.body.size(); index++) {
                p = (Point) src.body.get(index);
                dst.body.add(0, new Point(p.x, p.y));
                dst.body.add(0, new Point(j.p.x, j.p.y));
            }
        }
        
        /*
         * Take the points from src body in reverse order and add them to dst
         */
        if (src.tail == j && dst.tail == j) {
            for (index = src.body.size() - 1; index >= 0; index--) {
                p = (Point) src.body.get(index);
                dst.body.add(new Point(p.x, p.y));
                dst.body.add(new Point(j.p.x, j.p.y));
            }
        }
        
        /*
         * Take the points from src body in reverse order and add them to the
         * beginning of dst
         */
        if (src.head == j && dst.tail == j) {
            for (index = src.body.size() -1; index >= 0; index--) {
                p = (Point) src.body.get(index);
                dst.body.add(0, new Point(p.x, p.y));
                dst.body.add(0, new Point(j.p.x, j.p.y));
            }
        }
    }
    
    
    /*
     * 
     */
    public void rotate(int dir, int x, int y) {
        int index;
        Point p; 
        int cx, cy, tx, ty, cos, sin;
        
        cos = (int) Math.round(Math.cos(dir * Math.PI/2));
        sin = (int) Math.round(Math.sin(dir * Math.PI/2));
        
        for (index = 0; index < body.size(); index++) {
            p = (Point) body.get(index);
            cx = p.x; cy = p.y; cx -= x; cy -= y; tx = cx; ty = cy; 
            cx = tx * cos + ty * sin;  cy = -tx * sin + ty * cos;
            cx += x; cy += y; p.x = cx; p.y = cy;
        }
        
    }
    
    
    /*
     * Move the edge (one of its segments or corners really) to a new location
     */
    public Point place(int x, int y) {
        int cx, cy, dx, dy;
        Point p, p1 = null, p2 = null;
        
        if (hit_corner >= 0) {
            /*
             * Case where a corner is being moved
             */
            p = (Point) body.get(hit_corner);
            cx = p.x; cy = p.y; dx = x - cx; dy = y - cy;
            d.x = dx; d.y = dy;
            
            /*
             * If this is a unified move, then move all the subparts of the edge
             * else move only the hit corner.
             */
            if (unified_move) {
                move(dx, dy);
            } else {
                p.x = x; p.y = y;
            }
            
            return d;
        } else if (hit_segment >= 0) {
            /*
             * Case where a segment is being hit
             */
            if (!endSegment(hit_segment)) {
                p1 = (Point) body.get(hit_segment - 1);  
                p2 = (Point) body.get(hit_segment);
            } else {
                if (body.size() == 0) {
                    p1 = point(head);
                    p2 = point(tail);
                } else {
                    if (hit_segment == 0) {
                        p1 = point(head);
                        p2 = (Point) body.get(0);
                    } else if (hit_segment == body.size()) {
                        p1 = (Point) body.get(body.size() - 1);
                        p2 = point(tail);
                    }
                } 
            }
            
            /*
             * Unified move with a non-end segment.
             */
            if (unified_move) {
                /*
                 * Since this is a unified move, we need to take into 
                 * account the hit_context (point where this edge was first
                 * hit) to determinte the delta
                 */
                cx = p1.x; cy = p1.y; 
                dx = x - hit_context.x; dy = y - hit_context.y;
                d.x = dx; d.y = dy;
                move(dx, dy);
                hit_context.x += dx; hit_context.y += dy;
                return d;
            } 
            
            /*
             * Vertical segment is being moved
             */
            if (segment(hit_segment).vertical() && !endSegment(hit_segment)) {

                /*
                 * Constrain movement to in the x direction
                 */
                cx = p1.x; dx = x - cx; dy = 0; d.x = dx; d.y = dy;
                p1.x += dx; p2.x += dx;
                return d;
            }
            
            /*
             * Horizontal segment is being moved
             */
            if (segment(hit_segment).horizontal() && !endSegment(hit_segment)) {
                p1 = (Point) body.get(hit_segment - 1);  
                p2 = (Point) body.get(hit_segment);
 
                /*
                 * Constrain movement to in the y direction
                 */
                cy = p1.y; dx = 0; dy = y - cy; d.x = dx; d.y = dy;
                p1.y += dy; p2.y += dy;
             
                return d;        
            }   
        }
        return null;
    }
    
    
    /*
     * Move the internal nodes in the edge
     */
    public void move(int dx, int dy) {
        int index;
        Point t;
        SJunction junction;

        /*
         * Move the body of the edge only.
         */
        for (index = 0; index < body.size(); index++) {
            t = (Point) body.get(index);
            t.x += dx;
            t.y += dy;
        }
    }
    
    
    /*
     * Move a part of this edge (some subset of it's segments) in reaction to a
     * "parent" object moving.  For example, if a node object is moving and this
     * edge is attached orthogonally to that node, then the movement of that 
     * node would cause parts of this edge to be moved too (the attaching 
     * segments).  In this function, we move the necessary portions of the edge
     * in reaction to some attached object's movement
     */
    public void subordinateMove(SObject parent, int dx, int dy) {
        Point p1 = null, p2 = null;
        CLine s1 = null, s2 = null, line = null;
        int pindex_1 = -1, pindex_2 = -1;
        int sindex_1 = -1, sindex_2 = -1;
        int mx, my;
        int cdx = dx, cdy = dy;   /* Cached delta                    */
        Object thead, ttail;      /* Temporary head and tail objects */
        SNodeTerminal terminal;
        boolean h = false, v = false;
        Integer orientation;

        if (!multipoint) return;
        
        if (selected) return;
        
        thead = head; ttail = tail;
        
        /*
         * If and end of this edge is to be of type terminal, then we need the
         * node object for that terminal
         */
        if (head instanceof SNodeTerminal) {
            terminal = (SNodeTerminal) head;
            thead = terminal.parent;
        }
        
        if (tail instanceof SNodeTerminal) {
            terminal = (SNodeTerminal) tail;
            ttail = terminal.parent;
        }
        
        /*
         * Handle the case where this edge has no body parts.  In this case, we
         * might have to break the edge in the middle to allow orthogonal 
         * movement if the edge is straight.
         */
        if (body.size() < 1) {
            if (thead == ttail) return;
            /*
             * Since the edge has no body points, in order to make this move 
             * work we must "break" the edge in the middle.
             */
            line = segment(0);
            mx = (line.x1 + line.x2) / 2;
            my = (line.y1 + line.y2) / 2;
            
            /*
             * "Break" the edge by adding 2 points in the middle of the body.  
             * Now, when the edge is moved in response to a parent the move will 
             * be orthogonal.
             */
            orientation = (Integer) save_orientation.get(0);
            if (orientation  == CLine.HORIZONTAL && dy != 0) {
                body.add(new Point(mx, point(head).y));
                body.add(new Point(mx, point(tail).y));
                saveMoveState(null);
            } else if (orientation  == CLine.VERTICAL && dx != 0) {
                body.add(new Point(point(head).x, my));
                body.add(new Point(point(tail).x, my));
                saveMoveState(null);
            } 
 
            /*
             * Handle the corner case where the calculated 'break' point for the
             * edge is coincident on either the head or tail point. In this case
             * we do not break the edge at this time.  We need to wait until the 
             * head and tail are moved furthur apart so that the break point 
             * calculation results in a body that's orthogonal.
             */
            if (body.size() > 0 && 
               (save_orientation.get(0) == CLine.POINT || 
                save_orientation.get(2) == CLine.POINT )) {
                body.clear();
                save_orientation.clear();
                save_orientation.add(orientation);
            }
                        
            return;
        }
        
        /*
         * In cases where the same edge is attached to 2 different terminals of 
         * the same node, the subordinateMove function will have to do the work
         * of moving *2* body points instead of just one.  In this case, thead
         * and ttail will point to the same node.
         */
        if (thead == parent && ttail != parent) {
            s1 = headSegment();
            pindex_1 = 0;
            sindex_1 = 0;
        } else if (ttail == parent && thead != parent) {
            s1 = tailSegment();   
            pindex_1 = body.size() - 1;
            sindex_1 = body.size();
        } else if (thead == parent && ttail == parent) {
            s1 = headSegment();
            s2 = tailSegment();
            pindex_1 = 0;
            sindex_1 = 0;
            pindex_2 = body.size() - 1;
            sindex_2 = body.size();
        }
        
        /*
         * Calculate and apply the move offsets to the first segment
         */
        if (s1 != null) {
            p1 = (Point) body.get(pindex_1);
            h = save_orientation.get(sindex_1) == CLine.HORIZONTAL;
            v = save_orientation.get(sindex_1) == CLine.VERTICAL;

            if (v) dy = 0; else if (h) dx = 0;  
            if (v || h)  {
                p1.x += dx; p1.y += dy;
            }
        }

        /*
         * Revert the deltas back to the cached values since the above code may
         * have set either dx/dy to null
         */
        dx = cdx; dy = cdy;
        
        /*
         * Calculate and apply the move offsets to the second segment (if any)
         */
        if (s2 != null) {
            p2 = (Point) body.get(pindex_2);
            h = save_orientation.get(sindex_2) == CLine.HORIZONTAL;
            v = save_orientation.get(sindex_2) == CLine.VERTICAL;

            if (v) dy = 0; else if (h) dx = 0;
            if (v || h)  {
                p2.x += dx; p2.y += dy;
            }
        }     
    }
    
    
    /*
     * Save the move state of this edge before it moves as a response to a 
     * parent object's move.  For edge objects, the state consists of the 
     * orientation at which the end segments of the edge are attached to the
     * parent object.
     */
    public void saveMoveState(SObject parent) {
        int index;
        CLine s;
        
        save_orientation.clear();
        for (index = 0; index <= body.size(); index++) {
            s = segment(index);
            save_orientation.add(s.orientation());
        }
    }
    
    
    /*
     * Check to see if this edge is a hit by clicking (sx, sy).  This really 
     * means to check all the segments and corners of the edge to see if any one 
     * of them are hit.  
     */
    public boolean hit(int sx, int sy) {
        int sindex = 0, cindex = 0, index, lastx, lasty, currx, curry;
        Point p;
 
        hit_corner  = -1;
        hit_segment = -1;
  
        lastx = Schematic.c2s(point(head).x);
        lasty = Schematic.c2s(point(head).y);
        
        for (index = 0; index < body.size(); index++) {
            p = (Point) body.get(index);
            currx = Schematic.c2s(p.x);
            curry = Schematic.c2s(p.y);
            
            hpoint.x = currx;
            hpoint.y = curry;
            
            /*
             * First check to see if the corner is hit.  If not, then proceed to 
             * check if the segment is hit.
             */
            if (hpoint.distance((double)sx, (double)sy) < 5) {       
                hit_corner = cindex;
                return true;
            } else if (CLine.near(lastx, lasty, currx, curry, sx, sy, 1.3)) {
                hit_segment = sindex;
                return true;
            }
            
            cindex++;
            sindex++;
            
            lastx = currx;
            lasty = curry;
        }
  
        currx = Schematic.c2s(point(tail).x);
        curry = Schematic.c2s(point(tail).y);
        
        if (CLine.near(lastx, lasty, currx, curry, sx, sy, 1.3)) {
            hit_segment = sindex;
            return true;
        }
    
        return false;
    }
    
    
    /*
     * Set the hit context for this schematic edge.  For edges, the context is 
     * just the point where the edge was clicked (to figure out which segment of
     * the edge was clicked)
     */
    public void setHitContext(int cx, int cy) {
        hit_context.x = cx;
        hit_context.y = cy;
    }
    
    
    /*
     * Given a screen point, find the point on this edge to be suggested to the
     * schematic module.  This point can be either a corner or a segment of this 
     * edge.
     */
    public SEdge suggest(int sx, int sy) {
        int sindex = 0, cindex = 0, index, lastx, lasty, currx, curry;
        Point p, s;
        
        suggest_corner  = -1;
        suggest_segment = -1;
        suggestion = null;
  
        lastx = Schematic.c2s(point(head).x);
        lasty = Schematic.c2s(point(head).y);
        
        for (index = 0; index < body.size(); index++) {
            p = (Point) body.get(index);
            currx = Schematic.c2s(p.x);
            curry = Schematic.c2s(p.y);
            
            spoint.x = currx;
            spoint.y = curry;
            
            /*
             * First check to see if the corner is suggested.  If not, then 
             * proceed to check if the segment is suggested
             */
            if (spoint.distance((double)sx, (double)sy) < 5) {       
                suggest_corner = cindex;
                /*
                 * Convert back to schematic coordinates;
                 */
                spoint.x = Schematic.s2cr(spoint.x);
                spoint.y = Schematic.s2cr(spoint.y);
                suggestion = spoint;
                return this;
            } else if (CLine.near(lastx, lasty, currx, curry, sx, sy, 1.3)) {
                suggest_segment = sindex;
                
                /*
                 * We know which segment that is being suggested but we still 
                 * need to know the closest point on that segment from the 
                 * current mouse point, in schematic coordinates so we can 
                 * suggest that point
                 */
                s = closestpoint(lastx, lasty, currx, curry, sx, sy);
                
                /*
                 * Convert back to schematic coordinates;
                 */
                if (s != null) {
                    spoint.x = Schematic.s2cr(s.x);
                    spoint.y = Schematic.s2cr(s.y);
                    suggestion = spoint;
                    return this;
                }
            }
            
            cindex++;
            sindex++;
            
            lastx = currx;
            lasty = curry;
        }
       
        currx = Schematic.c2s(point(tail).x);
        curry = Schematic.c2s(point(tail).y);
        
        if (CLine.near(lastx, lasty, currx, curry, sx, sy, 1.3)) {
            suggest_segment = sindex;
            s = closestpoint(lastx, lasty, currx, curry, sx, sy);
            
            /*
             * Convert back to schematic coordinates;
             */
            if (s != null) {
                spoint.x = Schematic.s2cr(spoint.x);
                spoint.y = Schematic.s2cr(spoint.y);   
                suggestion = spoint;
                return this;
            }
        }
        
        return null;
    }
    
    
    /*
     * Given an end (head/tail), get the schematic point that the end is sitting
     * on
     */
    public Point point(Object end) {
        SNodeTerminal terminal;
        SJunction     junction;
        
        if (end instanceof SNodeTerminal) {
            terminal = (SNodeTerminal) end;
            return terminal.p;
        } else if (end instanceof SJunction) {
            junction = (SJunction) end;
            return junction.p;
        }  
        
        return null;
    }
    
    
    /*
     * Based on the move context (objects that are selected when the move is 
     * performed) we can figure out if we need to do a unified move or not: if
     * only this edge is selected, then we don't do a unified move.  If other
     * objects are selected along with this edge, then we do a unified move of
     * the edge
     */
    public void setMoveContext(Object obj) {
        Vector vector;
        int index;
        
        unified_move = false;
        
        if (obj instanceof Vector) {
            vector = (Vector) obj;
            if (vector.size() > 1 && vector.contains(this)) {
                unified_move = true;
            }
        }
    }
    
    
    /*
     * Add an internal point to the body of the edge
     */
    public void addPoint(int cx, int cy) {
        body.add(new Point(cx, cy));
    }
    
    
    /*
     * Given an object that is suggested to us by the schematic object, get the
     * object's suggestion point (to highlight).  How the suggestion point is
     * determined varies based on what type of object is being passed
     */
    public static Point getSuggestionPoint(Object object) {
        SNode node;
        SEdge edge;
        SJunction junction;
        SNodeTerminal terminal;
        
        if (object instanceof SNode) {
            node = (SNode) object;
            terminal = (SNodeTerminal) node.suggest_terminal;
            return terminal.p;
        } else if (object instanceof SJunction) {
            junction = (SJunction) object;
            return junction.p;
        } else if (object instanceof SEdge) {
            edge = (SEdge) object;
            return edge.suggestion;
        }
        return null;
    }
    
    
    /*
     * Get the bounding box for this edge.  We make a bounding box based on head
     * and tail only.  Should we be considering the internal points as well?
     */
    public Rectangle getBoundingBox() {
        SNodeTerminal thead, ttail;
        SJunction junction;
        Point h = point(head);
        Point t = point(tail);
        
        assert (h != null && t != null);

        if (h.x < t.x) box.x = h.x; else box.x = t.x;
        if (h.y < t.y) box.y = h.y; else box.y = t.y;
        box.width  = Math.abs(h.x - t.x);
        box.height = Math.abs(h.y - t.y);
        return box;
    }
    
    
    /*
     * Calculate the center of mass for this edge by calculating the center of 
     * mass for all of it's internal points including head and tail;
     */
    public Point2D.Float center() {
        int x, y, index;
        Point point;
        
        com.x = 0; com.y = 0;
        
        com.x += point(head).x; com.y += point(head).y;
        
        for (index = 0; index < body.size(); index++) {
            point = (Point) body.get(index);
            com.x += point.x; com.y += point.y;
        }
        
        com.x += point(tail).x; com.y += point(tail).y;
        
        com.x /= (float)(body.size() + 2);
        com.y /= (float)(body.size() + 2);
        
        return com;
    }
    
    
    /*
     * If the edge ends in a junction, there will be an unwanted body point that
     * will coincide with the tail junction itself.  remove the body point.
     */
    public void fixtail() {
        Point p;
        SJunction j;
        int size = body.size();
        
        if (size > 0) {
            p = (Point) body.get(size - 1);
            if (tail instanceof SJunction) {
                j = (SJunction) tail;
                if (j.p.x == p.x && j.p.y == p.y) {
                    body.remove(size - 1);
                }
            }
        }
    }
    
    
    /*
     * Check if the edge is "flattened" or not
     */
    public boolean flat() {
        return true;
    }
    
    
    /*
     * Flatten the edge
     */
    public void flatten() {
        
    }
    
    
    /*
     * Get the line object that makes up a certain segment in the edge.  
     */
    public CLine segment(int sindex) {
        Point lp = null, cp = null;
        int index;
        int tindex;
        boolean found = false;
        CLine segment = null;
        
        /*
         * We use multiple temoprary line objects as for computing the result 
         * since this function might be used to get access to multiple segments
         * of the same edge at one time.
         */
        tindex = (segment_index++) % max_segments;
        
        segment = segments[tindex];
        
        segment.clear();
        
        lp = point(head);
        
        /*
         * Walk to the specified segment
         */
        for (index = 0; index < body.size(); index++) {
           cp = (Point) body.get(index);
           if (index == sindex) break;
           lp = cp; 
        }
        
        if (sindex == body.size()) cp = point(tail);
        
        segment.x1 = lp.x; segment.y1 = lp.y;
        
        if (cp != null) {
            segment.x2 = cp.x; segment.y2 = cp.y;
        } else if (temp.x > 0) {
            segment.x2 = temp.x; segment.y2 = temp.y;
        }
        
        return segment;    
    }
    
    
    /*
     * Is an edge segment the one connected to head/tail?
     */
    public boolean endSegment(int sindex) {
        if (body.size() <= 1) return true;
        if (sindex == 0 || sindex == body.size()) return true;
        return false;
    }
    
    
    /*
     * Get the line object that corresponds to the first segment of the edge.
     */
    public CLine headSegment() {
        return segment(0);
    }
    
    
    /*
     * Get the line object that corresponds to the last segment of the edge.
     */
    public CLine tailSegment() {
        return segment(body.size());
    }
    
    
    /*
     * Get the closest point between a line defined by (x1, y1, x2, y2) and a 
     * point (x, y) 
     */
    public Point closestpoint(int x1, int y1, int x2, int y2, int x, int y) {
        double a, b, c, s, A;
        boolean near;
        Point temp;
        
        /*
         * First use Hero's formula to see if the point is reasonably close to
         * the line.
         */
        near = CLine.near(x1, y1, x2, y2, x, y, 1.05);

        /*
         * Then use the closest-point algorithm to find the point on the line 
         * that is closest to the point.
         */
        if (near) {
            temp = CLine.closestpoint(x1, y1, x2, y2, x, y);
            spoint.x = temp.x; spoint.y = temp.y;
            return spoint;
        } else {
            return null;
        }
    }
    
    
    /*
     * Check whether this edge is selectable or not based on how the end objects
     * are marked selected
     */    
    public boolean selectable() {
        SObject end;
        if (!mark_selected) return false;
        
        if (endobject(head).mark_selected && endobject(tail).mark_selected) {
            return true;
        }
        
        return false;
    }
    
    
    /*
     * 
     */
    public Point nodeIntersection(SNode node, int sindex) {
        CLine segment;
        int sx1, sy1, sx2, sy2;
        
        segment = segment(sindex);
        sx1 = Schematic.c2s(segment.x1); sy1 = Schematic.c2s(segment.y1);
        sx2 = Schematic.c2s(segment.x2); sy2 = Schematic.c2s(segment.y2);
       
        return node.boundsIntersection(sx1, sy1, sx2, sy2);
    }
    
    
    /*
     * Draw the edge
     */
    public void draw(Graphics g, Schematic schematic) {
        int index;
        int lastx = 0, lasty = 0, currx = 0, curry = 0;
        Point p;
        Graphics2D g2d;
        SNode node;
        SNodeTerminal terminal;
        CLine segment;
        Point intp;
        
        if (aa) {
            g2d = (Graphics2D) g;
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
                                 RenderingHints.VALUE_ANTIALIAS_ON);
        }
        
        if (selected) {
            g.setColor(schematic.selection_color);
        } else {
            g.setColor(Color.yellow);
        }
        
        if (head != null) {
            /*
             * If head is a node terminal, make sure to clip this edge at the
             * node bounds if the node wants its edges clipped
             */
            if (head instanceof SNodeTerminal) {
                terminal = (SNodeTerminal) head;
                node = terminal.parent;
                if (node.clip_edges) {
                    intp = nodeIntersection(node, 0);
                    if (intp != null) {
                        lastx = intp.x;
                        lasty = intp.y;
                    } else {
                        /*
                         * This should be an extremely rare case!
                         */
                        lastx = Schematic.c2s(point(head).x);
                        lasty = Schematic.c2s(point(head).y);
                    }
                } else {
                    lastx = Schematic.c2s(point(head).x);
                    lasty = Schematic.c2s(point(head).y);
                }
            } else {
                lastx = Schematic.c2s(point(head).x);
                lasty = Schematic.c2s(point(head).y);
            }
        }
        
        for (index = 0; index < body.size(); index++) {
            p = (Point) body.get(index);
            currx = Schematic.c2s(p.x);
            curry = Schematic.c2s(p.y);
            
            g.drawLine(lastx, lasty, currx, curry);
            
            lastx = currx;
            lasty = curry;
        }
       
        /*
         * If tail is null then it can only mean that this is a newly formed 
         * edge that is still being created, and the 'temp' point that is being
         * updated by mouse move should be used as the tail point.
         */
        if (tail != null) {
            /*
             * If tail is a node terminal, make sure to clip this edge at the
             * node bounds if the node wants its edges clipped
             */
            if (tail instanceof SNodeTerminal) {
                terminal = (SNodeTerminal) tail;
                node = terminal.parent;
                if (node.clip_edges) {
                    intp = nodeIntersection(node, body.size());
                    if (intp != null) {
                        currx = intp.x;
                        curry = intp.y;
                    } else {
                        /*
                         * This should be an extremely rare case!
                         */
                        currx = Schematic.c2s(point(tail).x);
                        curry = Schematic.c2s(point(tail).y);
                    }
                } else {
                    currx = Schematic.c2s(point(tail).x);
                    curry = Schematic.c2s(point(tail).y);
                }
            } else {
                currx = Schematic.c2s(point(tail).x);
                curry = Schematic.c2s(point(tail).y);
            }
            
            g.drawLine(lastx, lasty, currx, curry);
        } else if (temp.x >= 0) {
            currx = Schematic.c2s(temp.x);
            curry = Schematic.c2s(temp.y);
            
            g.drawLine(lastx, lasty, currx, curry);
        }
    }
    
    
    /*
     * Which sub-type of VCanvasObject
     */
    public String typeString() {
        return "[edge]";
    }
    
    
    /*
     * Dump the entire edge
     */
    public String dump() {
        String s = "";
        
        s += super.toString() + " ";
        s += "BODY: " + body.size() + " ";
        s += "HEAD: " + endobject(head).toString() + " ";
        s += "TAIL: " + endobject(tail).toString() + " ";

        return s;
    }
    
    
    /*
     * Presentation related
     */ 
    protected int   width;
    public    boolean multipoint;
    
    /* 
     * Main anatomical parts of a VCanvasEdge
     * 
     * head - head object
     * tail - tail object
     * body - body points
     */
    protected Object    head;
    protected Object    tail;
    protected vector    body;
    
    /*
     * Cached objects used for diff information
     */
    protected Object    new_head;
    protected Object    new_tail;
    protected vector    new_body;
    
    /*
     * Temporary objects used capping null-end edges; we need to know where the
     * old end objects were.  We populate these during checkpointing and null 
     * them out after capping.
     */
    protected Object temp_head;
    protected Object temp_tail;
    
    
    /*
     * temp              - if edge is being started but not yet finished, the 
     *                     temp point is used to follow around the mouse as it 
     *                     forms the wire.
     *                     
     * hit_corner_index  - if the edge is hit on a corner, this value holds the
     *                     index of that corner
     *                     
     * hit_segment_index - if the edge is hit on a segment, this value holds the 
     *                     index of that segment
     *                     
     * tsegment          - temporary double value line segment used to calculate 
     *                     nearest point on line
     *                     
     * segments          - temporary line objects
     * 
     * max_segments      - max number of line objects
     * 
     * segment_index     - current temp line index
     *                     
     * unified_move      - boolean to indicate whether, if a subpart of the edge
     *                     (corner/segment) is moved, if the other subparts will
     *                     move along with the edge.  We want a unified move if
     *                     the subpart that is hit will act as a leader for a 
     *                     move action, when there are more than just this edge
     *                     in the selection list
     */ 
    protected Point         temp;
    protected int           hit_corner;
    protected int           hit_segment;
    protected int           suggest_corner;
    protected int           suggest_segment;
    protected Point         suggestion;
    protected Line2D.Double tsegment;
    protected CLine         segments[];
    protected int           max_segments = 4;
    protected int           segment_index = 0;
    protected boolean       unified_move;
    protected Vector        save_orientation;
    protected Vector        temp_container;
}
